﻿using Microsoft.Scripting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Scripting.Utils;
using System.Threading;
using Scala.Compiler.Ast;
using Scala.Compiler.Types;
using System.Reflection;
using System.Reflection.Emit;

namespace Scala.Compiler.Binding
{
    public sealed class ScalaFunction
    {
        private object[] _defaults;
        private Type _returnType;
        private Type[] _paramTypes;
        internal IDictionary<object, object> _dict;
        internal MethodAttributes _attrs;
        private int _id;
        private string _name;

        [MultiRuntimeAware]
        private static int _currentId = 1;

        public ScalaFunction(string name, IDictionary<object, object> dict, object[] defaults)
        {
            _defaults = defaults ?? ArrayUtils.EmptyObjects;
            _name = name;
            _dict = dict;
        }

        public ScalaFunction(string name, DefDefStatement _defStat)
        {
            _name = name;
            DefStatement = _defStat;
            ScalaScope = _defStat.Scope.ParentScope;
        }

        public ScalaFunction(string name)
        {
            _name = name;
        }

        public Type ReturnType
        {
            get
            {
                if (_returnType == null)
                {
                    _returnType = GetReturnType();
                    return _returnType;
                }
                return _returnType;
            }
        }

        public Type[] ParamTypes
        {
            get
            {
                if (_paramTypes == null)
                {
                    _paramTypes = GetParamTypes();
                    return _paramTypes;
                }
                return _paramTypes;
            }
        }

        public MethodAttributes MethodAttributes
        {
            get { return _attrs; }
            set { _attrs = value; }
        }

        public object[] defaults { get { return _defaults; } set { _defaults = value; } }
        public string Name { get { return _name; } set { _name = value; } }
        public bool IsConstructor { get; set; }
        public MethodBuilder MethodBuilder { get; set; }
        public ConstructorBuilder ConstructorBuilder { get; set; }
        public DefDefStatement DefStatement { get; set; }
        public ScalaScope ScalaScope { get; set; }
        public IDictionary<object, object> Dict { get { return EnsureDict(); } set { _dict = value; } }

        internal IDictionary<object, object> EnsureDict()
        {
            if (_dict == null)
            {
                Interlocked.CompareExchange(ref _dict, new Dictionary<object, object>(), null);
            }
            return _dict;
        }

        internal void EnsureId()
        {
            if (_id == 0)
            {
                Interlocked.CompareExchange(ref _id, Interlocked.Increment(ref _currentId), 0);
            }
        }

        internal Type GetReturnType()
        {
            Type tp;
            if (!InternalTypes.GetType(DefStatement.TypeName,ScalaScope, out tp))
            {
                throw new Exception(String.Format("Undefined Return Type {0} of the function {1}", DefStatement.TypeName, this.Name));
            }
            return tp;
        }

        internal Type[] GetParamTypes()
        {
            Type[] tpArray = new Type[DefStatement.AllParams.Count];
            for (int i = 0; i < DefStatement.AllParams.Count; i++)
            {
                if (!InternalTypes.GetType(DefStatement.AllParams[i].TypeName, ScalaScope, out tpArray[i]))
                {
                    throw new Exception(String.Format("Undefined Parameter Type {0} of the parameter {1} in the function {2}", DefStatement.AllParams[i], DefStatement.AllParams[i].Name,this.Name));    
                }
            }
            return tpArray;
        }

        internal MethodAttributes GetAttributes()
        {
            return MethodAttributes.Public;
        }
    }
}
